feat(desktop): update UX with dedicated Updates tab#567
Conversation
Move updates into their own Settings tab as a single source of truth: a status hero with an affirmative up-to-date resting state, a Stable/ Preview channel picker (UI-only rename over the backend beta channel) with a downgrade confirmation, and honest 'Download updates automatically' copy matching the autoDownload backend semantics. Redesign UpdateDialog to share status/formatting helpers so the modal and the settings hero speak the same language. Delete the old inline UpdatesSection. Add periodic manifest polling: the app previously fetched the update manifest only once at boot, so long-running sessions never discovered new releases. It now re-checks every 6h (respecting the autoDownload toggle, skipping while a download is in flight) and clears the timer on quit.
✅ Deploy Preview for images-devsy-sh canceled.
|
📝 WalkthroughWalkthroughThe auto-updater now uses a stoppable initial delay plus recurring rechecks, and skips polling while an update is downloading or downloaded. The renderer adds shared update text/channel helpers, a new UpdatesPanel, UpdateDialog text updates, and SettingsPage wiring for a dedicated Updates tab. ChangesMain process updater recheck scheduling
Updates UI panel and shared helpers
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Updater as updater.ts
participant AutoUpdater as electron-updater
participant Index as index.ts
Updater->>Updater: schedule initial check after INITIAL_CHECK_DELAY_MS
Updater->>AutoUpdater: checkForUpdates()
loop every RECHECK_INTERVAL_MS
Updater->>Updater: runBackgroundCheck()
alt lastStatus is downloading or downloaded
Updater-->>Updater: skip check
else
Updater->>AutoUpdater: checkForUpdates()
end
end
Index->>Updater: stopAutoUpdater() on before-quit
Updater->>Updater: clear scheduled timers
sequenceDiagram
participant User
participant UpdatesPanel
participant ConfirmDialog
participant IPC as Main Process
User->>UpdatesPanel: select release channel
UpdatesPanel->>UpdatesPanel: isDowngrade(current, target)?
alt downgrade
UpdatesPanel->>ConfirmDialog: open confirmation
User->>ConfirmDialog: confirm
ConfirmDialog->>UpdatesPanel: applyChannel(target)
else not a downgrade
UpdatesPanel->>UpdatesPanel: applyChannel(target)
end
UpdatesPanel->>IPC: setReleaseChannel()
IPC-->>UpdatesPanel: success or failure
UpdatesPanel-->>UpdatesPanel: rollback selection on failure
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Deploy Preview for devsydev canceled.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
desktop/src/renderer/src/lib/components/update/status-copy.ts (1)
19-24: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMissing fallback for undefined
version.
UpdateStatus.versionis optional per the IPC contract, butstatusHeadlineinterpolatess.versiondirectly foravailable/downloading/downloaded, which would render as"Version undefined is available"if the field is ever omitted for these states.💡 Proposed defensive fallback
case "available": - return `Version ${s.version} is available` + return `Version ${s.version ?? "unknown"} is available` case "downloading": - return `Downloading v${s.version} · ${(s.progress?.percent ?? 0).toFixed(0)}%` + return `Downloading v${s.version ?? "?"} · ${(s.progress?.percent ?? 0).toFixed(0)}%` case "downloaded": - return `Version ${s.version} is ready to install` + return `Version ${s.version ?? "unknown"} is ready to install`🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/renderer/src/lib/components/update/status-copy.ts` around lines 19 - 24, The statusHeadline logic in status-copy.ts interpolates UpdateStatus.version directly for the available, downloading, and downloaded cases, which can surface “undefined” in the UI. Update the statusHeadline switch to defensively handle missing version values by using a safe fallback or conditional text for those states, and keep the formatting consistent with the existing s.progress handling in the downloading branch.desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte (2)
1-232: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftConsider adding component tests for
UpdatesPanel.This new component owns non-trivial async logic — downgrade confirmation flow, optimistic channel switch with revert-on-error, and IPC error toasts — none of which appear covered by a dedicated test file (unlike
UpdateDialog.test.ts). Given the PR's stated goal of comprehensive update-flow coverage, tests forrequestChannel/applyChannelbehavior (downgrade vs. upgrade paths, error revert) would guard this critical settings path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte` around lines 1 - 232, Add a dedicated test file for UpdatesPanel to cover its async channel-switch flow and IPC error handling. Focus on the requestChannel and applyChannel paths: verify downgrade selections open ConfirmDialog instead of switching immediately, verify normal upgrades call setReleaseChannelIpc and show success toasts, and verify failures revert releaseChannel and emit the error toast. Use the existing component behavior around markUserInitiated, getReleaseChannel, and channelLabel to locate the logic under test.
168-189: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd ARIA semantics to the channel picker.
The channel cards act as an exclusive-choice control but expose no
role/aria-checkedstate, so screen readers can't tell which channel is selected (only a visual border + checkmark indicate it).♿ Proposed fix
- <div class="grid grid-cols-2 gap-3"> + <div class="grid grid-cols-2 gap-3" role="radiogroup" aria-label="Release Channel"> {`#each` CHANNELS as c (c.value)} <button + type="button" + role="radio" + aria-checked={releaseChannel === c.value} class="rounded-lg border p-3 text-left transition-colors {releaseChannel === c.value ? 'border-primary bg-primary/5 ring-1 ring-primary' : 'border-border hover:border-muted-foreground/50'}" onclick={() => requestChannel(c.value)} >🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte` around lines 168 - 189, The channel picker buttons in UpdatesPanel.svelte are visually exclusive but չուն’t expose selection state to assistive tech. Update the button group rendered from CHANNELS to use proper ARIA semantics for a single-select control, and add the selected state on each option based on releaseChannel === c.value. Keep the existing requestChannel(c.value) behavior, but make each card announce its role and checked/selected status consistently with the current visual state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@desktop/src/main/updater.ts`:
- Around line 260-276: The initial background-check timeout in the updater flow
is not being cleaned up, so shutdown can still trigger a pending check. In the
runBackgroundCheck setup, store the handle returned by the initial setTimeout
alongside recheckTimer, and update stopAutoUpdater() to clear both timers before
nulling them out. Use the existing runBackgroundCheck and stopAutoUpdater
symbols to keep the fix localized.
---
Nitpick comments:
In `@desktop/src/renderer/src/lib/components/update/status-copy.ts`:
- Around line 19-24: The statusHeadline logic in status-copy.ts interpolates
UpdateStatus.version directly for the available, downloading, and downloaded
cases, which can surface “undefined” in the UI. Update the statusHeadline switch
to defensively handle missing version values by using a safe fallback or
conditional text for those states, and keep the formatting consistent with the
existing s.progress handling in the downloading branch.
In `@desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte`:
- Around line 1-232: Add a dedicated test file for UpdatesPanel to cover its
async channel-switch flow and IPC error handling. Focus on the requestChannel
and applyChannel paths: verify downgrade selections open ConfirmDialog instead
of switching immediately, verify normal upgrades call setReleaseChannelIpc and
show success toasts, and verify failures revert releaseChannel and emit the
error toast. Use the existing component behavior around markUserInitiated,
getReleaseChannel, and channelLabel to locate the logic under test.
- Around line 168-189: The channel picker buttons in UpdatesPanel.svelte are
visually exclusive but չուն’t expose selection state to assistive tech. Update
the button group rendered from CHANNELS to use proper ARIA semantics for a
single-select control, and add the selected state on each option based on
releaseChannel === c.value. Keep the existing requestChannel(c.value) behavior,
but make each card announce its role and checked/selected status consistently
with the current visual state.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ba5155fe-a2d2-42e3-92ea-dbc9f3638cd3
📒 Files selected for processing (12)
desktop/src/main/__tests__/updater.test.tsdesktop/src/main/index.tsdesktop/src/main/updater.tsdesktop/src/renderer/src/lib/components/update/UpdateDialog.sveltedesktop/src/renderer/src/lib/components/update/UpdateDialog.test.tsdesktop/src/renderer/src/lib/components/update/UpdatesPanel.sveltedesktop/src/renderer/src/lib/components/update/UpdatesSection.sveltedesktop/src/renderer/src/lib/components/update/channel.test.tsdesktop/src/renderer/src/lib/components/update/channel.tsdesktop/src/renderer/src/lib/components/update/status-copy.test.tsdesktop/src/renderer/src/lib/components/update/status-copy.tsdesktop/src/renderer/src/pages/SettingsPage.svelte
💤 Files with no reviewable changes (1)
- desktop/src/renderer/src/lib/components/update/UpdatesSection.svelte
- Clear the pending initial-check setTimeout in stopAutoUpdater so a quit within the boot delay can't fire a check during teardown. - Add radiogroup/radio ARIA semantics to the release-channel picker so the selected channel is exposed to assistive tech. - Guard statusHeadline against a missing version field. - Add UpdatesPanel tests covering the upgrade, downgrade-confirm, and IPC-failure-revert channel-switch paths, plus a boot-timer cleanup test for the updater.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
desktop/src/renderer/src/lib/components/update/UpdatesPanel.test.ts (1)
85-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDowngrade confirmation flow isn't tested end-to-end.
This test only checks that the confirm dialog appears and
setReleaseChannelhasn't fired yet. There's no coverage for clicking through the confirmation (verifyingsetReleaseChannel("stable")eventually fires) or canceling it (verifying the selection stays on Preview and no IPC call is made). Given the PR explicitly calls out downgrade-confirm as a reviewed path, closing this gap would meaningfully raise confidence in the new confirmation UX.Suggested additional coverage
it("opens a confirmation instead of switching on a downgrade", async () => { await renderPanel("beta") cardButton(/Stable/)?.click() await tick() // Downgrade must wait for confirmation — no IPC call yet. expect(setReleaseChannel).not.toHaveBeenCalled() expect(document.body.textContent).toMatch(/switch to the stable channel\?/i) }) + + it("confirms and switches on downgrade confirmation", async () => { + await renderPanel("beta") + cardButton(/Stable/)?.click() + await tick() + + // Click through the confirmation button (adjust selector to match component). + const confirmBtn = Array.from(document.querySelectorAll("button")).find((b) => + /confirm/i.test(b.textContent ?? ""), + ) + confirmBtn?.click() + await tick() + await Promise.resolve() + + expect(setReleaseChannel).toHaveBeenCalledWith("stable") + }) + + it("cancels the downgrade confirmation without switching", async () => { + await renderPanel("beta") + cardButton(/Stable/)?.click() + await tick() + + const cancelBtn = Array.from(document.querySelectorAll("button")).find((b) => + /cancel/i.test(b.textContent ?? ""), + ) + cancelBtn?.click() + await tick() + + expect(setReleaseChannel).not.toHaveBeenCalled() + expect(cardButton(/Preview/)?.getAttribute("aria-checked")).toBe("true") + })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/renderer/src/lib/components/update/UpdatesPanel.test.ts` around lines 85 - 94, The downgrade confirmation path in UpdatesPanel is only partially covered by the existing test in UpdatesPanel.test.ts. Extend the test around renderPanel, cardButton, and setReleaseChannel to cover the full confirmation flow: one case should click through the dialog and assert setReleaseChannel("stable") is eventually called, and another should cancel the dialog and assert the selected channel remains Preview with no IPC call. Keep the coverage anchored to the same confirmation UI text and release channel switching behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@desktop/src/renderer/src/lib/components/update/UpdatesPanel.test.ts`:
- Around line 85-94: The downgrade confirmation path in UpdatesPanel is only
partially covered by the existing test in UpdatesPanel.test.ts. Extend the test
around renderPanel, cardButton, and setReleaseChannel to cover the full
confirmation flow: one case should click through the dialog and assert
setReleaseChannel("stable") is eventually called, and another should cancel the
dialog and assert the selected channel remains Preview with no IPC call. Keep
the coverage anchored to the same confirmation UI text and release channel
switching behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: bd98d84a-1f4f-4fb8-b674-e9771035dbc4
📒 Files selected for processing (5)
desktop/src/main/__tests__/updater.test.tsdesktop/src/main/updater.tsdesktop/src/renderer/src/lib/components/update/UpdatesPanel.sveltedesktop/src/renderer/src/lib/components/update/UpdatesPanel.test.tsdesktop/src/renderer/src/lib/components/update/status-copy.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- desktop/src/renderer/src/lib/components/update/status-copy.ts
- desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte
- desktop/src/main/updater.ts
Summary
Re-imagines the desktop update and release-channel experience. Updates previously had no home — they were wedged into the General settings tab between proxy fields and a keyboard-shortcut table, and the same state was rendered across four disconnected surfaces.
Changes
stable/betavalues and electron-updater'sbetachannel untouched. Switching Preview → Stable now confirms first (it's a potential downgrade).autoDownloadsemantics (it downloads in the background but still needs a restart).UpdateDialogshares status/formatting helpers with the hero so the modal and settings tab speak the same language.UpdatesSection.Verification
svelte-check: 0 errors.Backend
updater.tsupdate logic and IPC are otherwise unchanged; no release-pipeline or feed changes required.Summary by CodeRabbit
New Features
Bug Fixes